package com.ctsi.framework.schema.manager.impl;

import com.ctsi.framework.common.exception.BaseException;
import com.ctsi.framework.schema.manager.DbSchemaManager;
import com.ctsi.framework.schema.manager.properties.SchemaManagerProperties;
import liquibase.Contexts;
import liquibase.Liquibase;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author: zhangjw
 * @Date: 2018/9/12 18:22
 * @Description: 基于liquibase的数据库迁移工具
 */
public class CommonDbSchemaManagerImpl implements DbSchemaManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(CommonDbSchemaManagerImpl.class);
    public static final String LIQUIBASE_CHANGELOG = "classpath*:/initdata/**.changelog.xml";

    private static List<Liquibase> instances = new ArrayList<>();

    private SchemaManagerProperties properties;
    private DataSource dataSource;
    private ResourceLoader resourceLoader;

    public CommonDbSchemaManagerImpl(SchemaManagerProperties properties,DataSource dataSource,ResourceLoader resourceLoader) {
        this.properties = properties;
        this.dataSource = dataSource;
        this.resourceLoader = resourceLoader;
        try {
            createLiquibaseInstance();
            // 排序包含schema的log大于包含data的log
            instances = instances.stream().sorted((x,y) ->{
                String xname  = x.getChangeLogFile().substring(x.getChangeLogFile().lastIndexOf("/")+1,x.getChangeLogFile().length());
                String yname  = y.getChangeLogFile().substring(y.getChangeLogFile().lastIndexOf("/")+1,y.getChangeLogFile().length());

                if (xname.contains("schema") && yname.contains("data")) {
                    return -1;
                } else if (xname.contains("data") && yname.contains("schema")) {
                    return 1;
                }
                return 0;
            }).collect(Collectors.toList());
            //instances.forEach(in -> System.out.println(in.getChangeLogFile()));
        }  catch (Exception e) {
            LOGGER.error("handler Liquibase changelogs occur error: {}",e);
        }

    }


    public void initSchema() {
        try {
            if (SchemaManagerProperties.DB_SCHEMA_UPDATE_CREATE_DROP.equals(properties.getMode())) {
                dbSchemaCreate();
            } else if (SchemaManagerProperties.DB_SCHEMA_UPDATE_DROP_CREATE.equals(properties.getMode())) {
                dbSchemaDrop();
                dbSchemaCreate();
            } else if (SchemaManagerProperties.DB_SCHEMA_UPDATE.equals(properties.getMode())) {
                dbSchemaUpdate();
            } else if (SchemaManagerProperties.DB_SCHEMA_CHECK.equals(properties.getMode())) {
//                Liquibase liquibase = createLiquibaseInstance();
//                liquibase.validate();
                for (Liquibase liquibase : instances) {
                    liquibase.validate();
                }
            }
        } catch (Exception e) {
            throw new BaseException("Error initialising app data model", e);
        }
    }

    protected void createLiquibaseInstance() throws Exception {

        Connection jdbcConnection = dataSource.getConnection();
        if (!jdbcConnection.getAutoCommit()) {
            jdbcConnection.commit();
        }
        DatabaseConnection connection = new JdbcConnection(jdbcConnection);
        Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
        database.setDatabaseChangeLogLockTableName(Database.databaseChangeLogLockTableName);
        database.setDatabaseChangeLogTableName( Database.databaseChangeLogTableName);
        String databaseSchema = properties.getSchemaName();
        if (StringUtils.isNotEmpty(databaseSchema)) {
            database.setDefaultSchemaName(databaseSchema);
            database.setLiquibaseSchemaName(databaseSchema);
        }

        String databaseCatalog = properties.getSchemaName();
        if (StringUtils.isNotEmpty(databaseCatalog)) {
            database.setDefaultCatalogName(databaseCatalog);
            database.setLiquibaseCatalogName(databaseCatalog);
        }
        createLiquibaseInstance(database);
    }

    /**
     * 采用多实例模式兼容多模块数据初始化设计
     * @param database
     * @throws Exception
     */
    public void createLiquibaseInstance(Database database) throws Exception {
        //Liquibase lb = new Liquibase(LIQUIBASE_CHANGELOG, new ClassLoaderResourceAccessor(), database);
        // 接用spring liquibase
//        SpringLiquibase liquibase = new SpringLiquibase();
//
//        liquibase.setResourceLoader(resourceLoader);
//        file:/C:/cloud-platform/cloud-system-modules/system-engine/out/production/resources/initdata/sys.changelog.xml

        Resource[] resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources(LIQUIBASE_CHANGELOG);
        for (Resource resource : resources) {
            String lopath = resource.getURI().toString();
            String logName = lopath.substring(lopath.lastIndexOf("/")+1,lopath.length());
            String prefix = logName.substring(0,logName.indexOf("."));

            String realpath = "initdata/" + logName;
            LOGGER.warn("Liquibase handler changeLog path: {}",realpath);
            Liquibase lb = new Liquibase(realpath, new ClassLoaderResourceAccessor(), database);
            instances.add(lb);
        }
//        SpringLiquibase.SpringResourceOpener opener = liquibase.new SpringResourceOpener(LIQUIBASE_CHANGELOG);
//        Liquibase lb = new Liquibase(LIQUIBASE_CHANGELOG, opener, database);

//        lb.setIgnoreClasspathPrefix(false);
//        return lb;
    }

    @Override
    public void dbSchemaCreate() {
        try {
//            Liquibase liquibase = createLiquibaseInstance();
//            liquibase.update(new Contexts());
            for (Liquibase liquibase : instances) {
                liquibase.update(new Contexts());
            }
        } catch (Exception e) {
            throw new BaseException("Error creating App model tables", e);
        }
    }

    @Override
    public void dbSchemaDrop() {
        try {
//            Liquibase liquibase = createLiquibaseInstance();
//            liquibase.dropAll();
            for (Liquibase liquibase : instances) {
                liquibase.dropAll();
            }
        } catch (Exception e) {
            LOGGER.info("Error dropping App model tables", e);
        }

    }

    @Override
    public String dbSchemaUpdate() {
        try {
//            Liquibase liquibase = createLiquibaseInstance();
//            liquibase.update(new Contexts());
            for (Liquibase liquibase : instances) {
                liquibase.update(new Contexts());
            }
        } catch (Exception e) {
            throw new BaseException("Error updating App model tables", e);
        }
        return null;
    }
}

